#include "vis_log.h"
#include "cairo-svg.h"
#include <cstdio>
#include <unistd.h>
#include <vector>
#include <map>
#include <cmath>
#include <cfloat>

// рисование "обычного" графика для данного датчика с помощью библиотеки Cairo:
//
// cr_img - cairo-контекст
// [(min_date,min_time);(max_date,max_time)] - интервал времени, для которого надо отобразить результаты замеров;
//                                             '***_date' задаются в днях, '***_time' - в секундах
// dat_beg - Х-я координата (в пикселях) точки, соответствующей (min_date,min_time)
// dat_scale - коэффициент, на который умножаются все Х-е координаты; нужен для перевода секунд в пиксели
// max_val - максимальное значение показаний всех датчиков из заданного временного интервала
// val_beg - Y-я координата (в пикселях) точки, соответствующей значению 'max_val'
// val_scale - коэффициент, на который умножаются все Y-е координаты; нужен для перевода double'ов в пиксели
// y_undef - Y-я координата (в пикселях) всех точек, в которых значение датчика равно "undefined"
void Sensor::Draw (void *cr_img, const unsigned int min_date, const unsigned int min_time, const unsigned int max_date, 
		   const unsigned int max_time, const double dat_beg, const double dat_scale, const double max_val, 
		   const double val_beg, const double val_scale, const int y_undef) const {
    cairo_t *img=static_cast<cairo_t*>(cr_img);
    unsigned int i,start_i=0u,end_i,j,start_j=0u;
    bool line=false; // рисуется линия или точка
    double x,y,prev_x=0.0,prev_y=0.0;
    
    for ( ; start_i!=num; ++start_i)
    {
	if ((dates[start_i]>min_date) || ((dates[start_i]==min_date) && (times[start_i]>=min_time))) break;
	if (!undefined[start_i]) ++start_j;
    }
    for (end_i=start_i; end_i!=num; ++end_i)
    {
	if ((dates[end_i]>max_date) || ((dates[end_i]==max_date) && (times[end_i]>max_time))) break;
    }
    if (start_i==end_i) return; // ни один замер не попал в нужный временной интервал
    
    j=start_j;
    for (i=start_i; i!=end_i; ++i)
    {
	x=dat_beg+static_cast<double>((dates[i]-min_date)*86400u+times[i]-min_time)*dat_scale;
	if (undefined[i])
	{
	    cairo_move_to(img,x,y_undef);
	    cairo_arc(img,x,y_undef,1.0,0.0,M_PI+M_PI);
	    cairo_arc(img,x,y_undef,1.2,0.0,M_PI+M_PI);
	    cairo_stroke(img); // рисуем всё, что надо было, потому что сейчас поменяется цвет "карандаша"
	    cairo_save(img); // сохраняем текущий цвет
	    cairo_set_source_rgba(img,1.0,0.0,0.0,1.0);
	    cairo_arc(img,x,y_undef,4.0,0.0,M_PI+M_PI); // обводим 'undefined' красным цветом
	    cairo_stroke(img); // сразу рисуем
	    cairo_restore(img); // восстанавливаем цвет
	    line=false;
	}
	else
	{
	    y=val_beg+(max_val-values[j])*val_scale;
	    cairo_move_to(img,x,y);
	    cairo_arc(img,x,y,1.0,0.0,M_PI+M_PI);
	    cairo_arc(img,x,y,1.2,0.0,M_PI+M_PI);
	    if (line)
	    {
		cairo_move_to(img,prev_x,prev_y);
		cairo_line_to(img,x,y);
	    }
	    line=true;
	    prev_x=x;
	    prev_y=y;
	    ++j;
	}
    }
}

void Sensor::DrawAvg1 (void *cr_img, const unsigned int min_date, const unsigned int min_time, const unsigned int max_date, 
		       const unsigned int max_time, const unsigned int mean_period, const double dat_beg, const double dat_scale, 
		       const double max_val, const double val_beg, const double val_scale, const double lower_bnd) const {
    cairo_t *img=static_cast<cairo_t*>(cr_img);
    unsigned int start_i=0u,end_i,start_j=0u,i,i1,j,date_end=min_date,time_end=min_time,num1;
    bool line=false; // рисуется линия или точка
    double mean,x,y1,y2,prev_x=0.0,prev_y1=0.0,prev_y2=0.0;
    
    for ( ; start_i!=num; ++start_i)
    {
	if ((dates[start_i]>min_date) || ((dates[start_i]==min_date) && (times[start_i]>=min_time))) break;
	if (!undefined[start_i]) ++start_j;
    }
    for (end_i=start_i; end_i!=num; ++end_i)
    {
	if ((dates[end_i]>max_date) || ((dates[end_i]==max_date) && (times[end_i]>max_time))) break;
    }
    if (start_i==end_i) return; // ни один замер не попал в нужный временной интервал
    
    i=start_i;
    j=start_j;
    while (i!=end_i)
    {
	time_end+=mean_period;
	num1=time_end/86400u;
	date_end+=num1;
	time_end-=(num1*86400u);
	num1=0u;
	mean=0.0;
	for (i1=i; i1!=end_i; ++i1)
	{
	    if (undefined[i1]) continue;
	    if ((dates[i1]>date_end) || ((dates[i1]==date_end) && (times[i1]>time_end))) break;
	    mean+=values[j+num1];
	    ++num1;
	}
	if (num1==0u)
	{
	    i=i1;
	    continue;
	}
	mean/=static_cast<double>(num1);
	for ( ; i!=i1; ++i)
	{
	    if (undefined[i])
	    {
		line=false;
		continue;
	    }
	    // mean +- |values[j]-mean| == mean +- (values[j]-mean)
	    y1=val_beg+(max_val-(mean+mean-values[j]))*val_scale;
	    y2=val_beg+(max_val-values[j])*val_scale;
	    if (y2<y1)
	    {
		x=y2;
		y2=y1;
		y1=x;
	    }
	    if (y1<val_beg) y1=val_beg;
	    if (y2>lower_bnd) y2=lower_bnd;
	    x=dat_beg+static_cast<double>((dates[i]-min_date)*86400u+times[i]-min_time)*dat_scale;
	    cairo_move_to(img,x,y1);
	    cairo_line_to(img,x,y2);
	    if (line)
	    {
		cairo_line_to(img,prev_x,prev_y2);
		cairo_line_to(img,prev_x,prev_y1);
		cairo_line_to(img,x,y1);
		cairo_fill(img);
	    }
	    else cairo_stroke(img);
	    line=true;
	    prev_x=x;
	    prev_y1=y1;
	    prev_y2=y2;
	    ++j;
	}
    }
}

void Sensor::DrawAvg2 (void *cr_img, const unsigned int min_date, const unsigned int min_time, const unsigned int max_date, 
		       const unsigned int max_time, const unsigned int mean_period, const double dat_beg, const double dat_scale, 
		       const double max_val, const double val_beg, const double val_scale, const double y_undef) const {
    cairo_t *img=static_cast<cairo_t*>(cr_img);
    unsigned int start_i=0u,end_i,start_j=0u,i,i1,j,date_end=min_date,time_end=min_time,num1;
    bool line=false; // рисуется линия или точка
    double x,y,prev_x=0.0,prev_y=0.0;
    
    for ( ; start_i!=num; ++start_i)
    {
	if ((dates[start_i]>min_date) || ((dates[start_i]==min_date) && (times[start_i]>=min_time))) break;
	if (!undefined[start_i]) ++start_j;
    }
    for (end_i=start_i; end_i!=num; ++end_i)
    {
	if ((dates[end_i]>max_date) || ((dates[end_i]==max_date) && (times[end_i]>max_time))) break;
    }
    if (start_i==end_i) return; // ни один замер не попал в нужный временной интервал
    
    i=start_i;
    j=start_j;
    while (i!=end_i)
    {
	time_end+=mean_period;
	num1=time_end/86400u;
	date_end+=num1;
	time_end-=(num1*86400u);
	num1=0u;
	y=0.0;
	for (i1=i; i1!=end_i; ++i1)
	{
	    if (undefined[i1]) continue;
	    if ((dates[i1]>date_end) || ((dates[i1]==date_end) && (times[i1]>time_end))) break;
	    y+=values[j+num1];
	    ++num1;
	}
	if (num1==0u)
	{
	    i=i1;
	    continue;
	}
	y=val_beg+(max_val-y/static_cast<double>(num1))*val_scale;
	for ( ; i!=i1; ++i)
	{
	    x=dat_beg+static_cast<double>((dates[i]-min_date)*86400u+times[i]-min_time)*dat_scale;
	    if (undefined[i])
	    {
		cairo_move_to(img,x,y_undef);
		cairo_arc(img,x,y_undef,1.0,0.0,M_PI+M_PI);
		cairo_arc(img,x,y_undef,1.2,0.0,M_PI+M_PI);
		cairo_stroke(img); // рисуем всё, что надо было, потому что сейчас поменяется цвет "карандаша"
		cairo_save(img); // сохраняем текущий цвет
		cairo_set_source_rgba(img,1.0,0.0,0.0,1.0);
		cairo_arc(img,x,y_undef,4.0,0.0,M_PI+M_PI); // обводим 'undefined' красным цветом
		cairo_stroke(img); // сразу рисуем
		cairo_restore(img); // восстанавливаем цвет
		line=false;
	    }
	    else
	    {
		cairo_move_to(img,x,y);
		cairo_arc(img,x,y,1.0,0.0,M_PI+M_PI);
		cairo_arc(img,x,y,1.2,0.0,M_PI+M_PI);
		if (line)
		{
		    cairo_move_to(img,prev_x,prev_y);
		    cairo_line_to(img,x,y);
		}
		line=true;
		prev_x=x;
		prev_y=y;
		++j;
	    }
	}
    }
}

// перевод даты в формате гггг-мм-дд в дни
unsigned int date_to_days (const unsigned int, const unsigned int, const unsigned int);

// перевод даты в днях в формат гггг-мм-дд
void days_to_date (const unsigned int, unsigned int&, unsigned int&, unsigned int&);

// перевод времени в формате чч:мм:сс в секунды
unsigned int time_to_seconds (const unsigned int, const unsigned int, const unsigned int);

// перевод времени в секундах в формат чч:мм:сс
void seconds_to_time (const unsigned int, unsigned int&, unsigned int&, unsigned int&);

// получение временного интервала из командной строки
unsigned int get_time_intl_in_seconds (const char*);

// структура для хранения полной даты
typedef struct {
    unsigned int days,seconds; // дата переводится в дни и секунды
} Date;

// построение списка запросов на графики датчиков
void build_sens_list_query (const char*&, std::map<unsigned int,std::vector<Date> >&);

// считывание строки из текствого файла ('\n' затирается);
// последний вызов должен быть с NULL в качестве входного аргумента!
char* getline (FILE*);

// разбор одной строки лог-файла
bool parse_log_line (const char*, unsigned int&, unsigned int&, 
		     const std::map<unsigned int,std::vector<Date> >&, 
		     unsigned int*, bool&, double*);


int main (int argc, char **argv)
{
    if (argc<4)
    {
	printf("\nUsage:\n  %s <path_to_log_file> <time_interval> \"<sensor_list>\" [-t|-f|-outp|-y|-v]\n\n"
	       "  <path_to_log_file> - relative or full path to file with logging info\n"
	       "  <time_interval>    - <hours>-<minutes>-<seconds>\n"
	       "                       for example, '32-40-0' means time interval of 32 hours 40 minutes\n"
	       "                       note that one can write '40-0' which entirely equals to '0-40-0',\n"
	       "                       and even '40-67' which entirely equals to '41-07'\n"
	       "  \"<sensor_list>\"    - (\"\" or '' are necessary!) a list of pairs <sensor_ID> <start_time>,\n"
	       "                       separated by non-numeric symbol(s)\n"
	       "                       <sensor_ID>  - an integer representing an ID of a sensor\n"
	       "                       <start_time> - a time label of the format\n"
	       "                                      <year>-<month>-<day>-<hour>-<minute>-<second>,\n"
	       "                                      which defines the first point of graph building\n"
	       "                       for example, \"10 2012-12-08-23-06-00; 17 2012-12-08-23-00-01\"\n\n"
	       "  -avg=<hours>-<minutes>-<seconds> - define time interval for averaging of values and make the program enable\n"
	       "                                     special drawing of graphs: mean values are represented\n"
	       "                                     as compound lines and variances - as filled areas;\n"
	       "                                     -avg=0 or -avg=1 behaves like averaging is disabled\n"
	       "  -t - output image will have transparent background (default is 'off')\n"
	       "  -f=<fmt> - set output image format to <fmt>; <fmt> can be 'png' or 'svg'; (default is 'png')\n"
	       "  -outp=<filename> - save output image to file <filename>.<fmt>;\n"
	       "                     <filename> should be without an extension; (default is 'sensors')\n"
	       "  -y - answer 'yes' to all questions (for example, file existance)\n"
	       "  -v - verbose mode (default is 'off')\n\n",argv[0]);
	return 0;
    }
    
    unsigned int period=get_time_intl_in_seconds(argv[2]); // интервал наблюдения за датчиками (в секундах)
    
    if (period==0xffffffff)
    {
	printf("\nError in <time_interval> argument\n\n");
	return 1;
    }
    if (period==0u) period=1u;
    
    unsigned int mean_period=0xffffffff; // интервал усреднения значений
    bool verbose=false,transp_bck=false,png_fmt=true,yes=false;
    char *outp_file=NULL,*s;
    int i;
    
    for (i=4; i<argc; ++i)
    {
	if (argv[i][0]=='-')
	{
	    switch (argv[i][1])
	    {
	      case 'v':
		  if (argv[i][2]=='\0')
		  {
		      verbose=true;
		      continue;
		  }
		  break;
	      case 't':
		  if (argv[i][2]=='\0')
		  {
		      transp_bck=true;
		      continue;
		  }
		  break;
	      case 'y':
		  if (argv[i][2]=='\0')
		  {
		      yes=true;
		      continue;
		  }
		  break;
	      case 'f':
		  if (argv[i][2]!='=')
		      break;
		  if (!strcmp(argv[i]+3,"svg"))
		  {
		      png_fmt=false;
		      continue;
		  }
		  if (!strcmp(argv[i]+3,"png"))
		  {
		      png_fmt=true;
		      continue;
		  }
		  if (outp_file!=NULL) free(outp_file);
		  printf("\nWrong <fmt> '%s' in -f\n\n",argv[i]+3);
		  return 1;
	      case 'o':
		  if (strncmp(argv[i]+2,"utp=",4lu)) break;
		  s=argv[i]+6;
		  for ( ; ; )
		  {
		      if ((*s=='\0') || !((*s==' ') || (*s=='\t') || (*s=='\n') || (*s=='\r') || (*s=='\f') || (*s=='\v')))
			  break;
		      ++s;
		  }
		  if (outp_file!=NULL) free(outp_file);
		  if (*s=='\0')
		  {
		      printf("\nEmpty <filename> in -outp\n\n");
		      return 1;
		  }
		  outp_file=static_cast<char*>(malloc((strlen(argv[i])-1lu)*sizeof(char)));
		  strcpy(outp_file,argv[i]+6);
		  continue;
	      case 'a':
		  if (strncmp(argv[i]+2,"vg=",3lu)) break;
		  mean_period=get_time_intl_in_seconds(argv[i]+5);
		  if (mean_period==0xffffffff)
		  {
		      if (outp_file!=NULL) free(outp_file);
		      printf("\nError in -avg\n\n");
		      return 1;
		  }
		  if (mean_period==0u) mean_period=1u;
		  continue;
	      default: break;
	    }
	}
	if (outp_file!=NULL) free(outp_file);
	printf("\nWrong input parameter '%s'\n\n",argv[i]);
	return 1;
    }
    if (outp_file==NULL)
    {
	outp_file=static_cast<char*>(malloc(12u*sizeof(char)));
	strcpy(outp_file,"sensors");
    }
    strcat(outp_file,png_fmt? ".png" : ".svg");
    
    if (verbose)
    {
	unsigned int _1;
	printf("\nProgram input:\n"
	       "  Averaging:             ");
	if (mean_period==0xffffffff) printf(" off");
	else
	{
	    unsigned int _2,_3;
	    seconds_to_time(mean_period,_1,_2,_3);
	    if (_1!=0u) printf(" %u hour(s)",_1);
	    if (_2!=0u) printf(" %u minute(s)",_2);
	    if (_3!=0u) printf(" %u second(s)",_3);
	}
	printf("\n  Transparent background: %s\n",transp_bck? "on" : "off");
	char *path=getcwd(NULL,512u);
	printf("  Output file:            %s",path);
	_1=strlen(path);
	if ((path[_1-1u]!='/') && (path[_1-1u]!='\\'))
	    putc((strchr(path,'\\')!=NULL)? '\\' : '/',stdout);
	free(path);
	printf("%s\n",outp_file);
	printf("  'Yes' to all questions: %s\n",yes? "on" : "off");
    }
    
    FILE *f=fopen(outp_file,"r");
    
    if (f!=NULL)
    {
	fclose(f);
	printf("\nFile '%s' already exists. Overwrite [y/n]? ",outp_file);
	if (yes) printf("y\n\n");
	else
	{
	    const char ans=getchar();
	    if ((ans!='y') && (ans!='Y'))
	    {
		free(outp_file);
		putc('\n',stdout);
		return 0;
	    }
	    getchar();
	    putc('\n',stdout);
	}
    }
    else
    {
	if (verbose) putc('\n',stdout);
    }
    f=fopen(outp_file,"w");
    if (f==NULL)
    {
	printf("\nCannot open output file '%s' for writing\n\n",outp_file);
	free(outp_file);
	return 1;
    }
    fclose(f);
    remove(outp_file); // т.к. fopen(...,"w") создала его
    
    f=fopen(argv[1],"r");
    if (f==NULL)
    {
	free(outp_file);
	printf("\nFile '%s' doesn't exist or cannot be opened for reading\n\n",argv[1]);
	return 1;
    }
    
    const char *argv_3=argv[3]; // заведение новой переменной нужно!
    std::map<unsigned int,std::vector<Date> > sens_list;
    
    if (verbose) printf("Building sensors' query list... ");
    do
	build_sens_list_query(argv_3,sens_list);
    while (*argv_3!='\0');
    if (verbose) printf("done.\n\n");
    if (sens_list.empty())
    {
	free(outp_file);
	fclose(f);
	return 0;
    }
    
    unsigned int days,seconds;
    bool undefined;
    unsigned int sens_num;
    double value;
    std::map<unsigned int,Sensor> sensors;
    
    if (verbose) printf("Parsing log file...\n");
    for (unsigned int line_ind=0u; !feof(f); ++line_ind)
    {
	s=getline(f);
	if (s!=NULL)
	{
	    if (parse_log_line(s,days,seconds,sens_list,&sens_num,undefined,&value))
		sensors[sens_num].Add(days,seconds,undefined,value);
	    else
	    {
		if (verbose) printf("  line %u was ignored\n",line_ind+1u);
	    }
	}
    }
    if (verbose) printf("Done.\n");
    getline(NULL);
    fclose(f);
    if (sensors.empty())
    {
	free(outp_file);
	return 0;
    }
    
    std::map<unsigned int,Sensor>::const_iterator it,it_end;
    std::map<unsigned int,std::vector<Date> >::const_iterator it1,it1_end;
    unsigned int min_date=0xffffffff,min_time=0xffffffff,max_date=0u,max_time=0u,time;
    double min_val=DBL_MAX,max_val=DBL_MIN;
    
    for (it1=sens_list.begin(),it1_end=sens_list.end(); it1!=it1_end; ++it1)
    {
	for (sens_num=0u; sens_num!=it1->second.size(); ++sens_num)
	{
	    days=it1->second[sens_num].days;
	    min_date=(days<min_date)? days : min_date;
	    max_date=(days>max_date)? days : max_date;
	    seconds=it1->second[sens_num].seconds;
	    min_time=(seconds<min_time)? seconds : min_time;
	    max_time=(seconds>max_time)? seconds : max_time;
	}
    }
    max_time+=period;
    days=max_time/86400u;
    max_date+=days;
    max_time-=(days*86400u);
    
    for (it=sensors.begin(),it_end=sensors.end(); it!=it_end; ++it)
	it->second.GetMinMaxVal(min_date,min_time,max_date,max_time,min_val,max_val);
    if (min_val==DBL_MAX)
    {
	free(outp_file);
	if (verbose)
	{
	    days_to_date(min_date,days,time,seconds);
	    printf("\nNo measurement was made from %u-%u%u-%u%u ",days,time/10u,time%10u,seconds/10u,seconds%10u);
	    seconds_to_time(min_time,days,time,seconds);
	    printf("%u%u:%u%u:%u%u to ",days/10u,days%10u,time/10u,time%10u,seconds/10u,seconds%10u);
	    days_to_date(max_date,days,time,seconds);
	    printf("%u-%u%u-%u%u ",days,time/10u,time%10u,seconds/10u,seconds%10u);
	    seconds_to_time(max_time,days,time,seconds);
	    printf("%u%u:%u%u:%u%u\n\n",days/10u,days%10u,time/10u,time%10u,seconds/10u,seconds%10u);
	}
	return 0;
    }
    
    const int pixels_for_symbol=10; // размер шрифта в пикселях (для моноширинного)
    const int hor_step_in_pixels=30; // расстояние (в пикселях) между концом одной подписи оси времени и началом другой
    const int vert_step_in_pixels=40; // расстояние по вертикали (в пикселях) между двумя подписями оси значений
    const int offset=20; // поля изображения (с четырёх сторон)
    const int val_num_lim=9; // количество подписей оси значений
    const int time_num_lim=((period<18u)? static_cast<int>(period+1u) : 19); // количество подписей оси времени
    // расстояние (в пикселях) между двумя засечками на оси времени
    const double per_step=static_cast<double>(period)/static_cast<const double>(time_num_lim-1);
    int width,height; // ширина и высота изображения
    char *s_end;
    int max_num_length=2; // максимальная длина подписи оси значений (в пикселях)
    int len;
    double val_step;
    
    s=static_cast<char*>(malloc(100lu*sizeof(char)));
    
    val_step=static_cast<double>(val_num_lim-1)*0.1;
    if (fabs(max_val-min_val)<val_step) min_val=max_val-val_step; // максимум и минимум из всех значений очень мало различаются
    val_step=(max_val-min_val)/static_cast<const double>(val_num_lim-1);
    for (i=0,value=min_val; i<val_num_lim; ++i,value+=val_step)
    {
	sprintf(s,"%.10lf",value);
	Sensor::GetRealNumEnd(s,s_end);
	len=static_cast<int>(s_end-s);
	max_num_length=(max_num_length<len)? len : max_num_length;
    }
    max_num_length*=(pixels_for_symbol*3/5); // перевод в пиксели
    
    height=offset+(pixels_for_symbol+vert_step_in_pixels)*val_num_lim-vert_step_in_pixels+offset;
    width=offset+(6*pixels_for_symbol+8+hor_step_in_pixels)*time_num_lim-hor_step_in_pixels+offset+max_num_length;
    
    cairo_surface_t *img_surface;
    cairo_t *img;
    
    if (png_fmt) img_surface=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,width,height);
    else img_surface=cairo_svg_surface_create(outp_file,width,height);
    img=cairo_create(img_surface);
    /* фон */
    cairo_set_source_rgba(img,1.0,1.0,1.0,transp_bck? 0.0 : 1.0);
    cairo_rectangle(img,0,0,width,height);
    cairo_fill(img);
    /* оси */
    cairo_set_source_rgba(img,0.0,0.0,0.0,1.0);
    cairo_move_to(img,offset+max_num_length+5,offset);
    cairo_rel_line_to(img,0,height-offset-offset);
    cairo_rel_line_to(img,width-offset-offset-max_num_length,0);
    len=offset+5;
    for (i=0; i<val_num_lim; ++i,len+=(pixels_for_symbol+vert_step_in_pixels))
    {
	cairo_move_to(img,offset+max_num_length+2,len);
	cairo_rel_line_to(img,6,0);
    }
    len=offset+max_num_length+3*pixels_for_symbol+4+5;
    for (i=0; i<time_num_lim; ++i,len+=(6*pixels_for_symbol+8+hor_step_in_pixels))
    {
	cairo_move_to(img,len,height-offset-3);
	cairo_rel_line_to(img,0,6);
    }
    cairo_stroke(img);
    /* подписи оси значений */
    cairo_set_font_size(img,pixels_for_symbol);
    value=90.0/255.0;
    cairo_set_source_rgba(img,value,value,value,1.0);
    len=offset+9;
    for (i=0,value=max_val; i<val_num_lim; ++i,value-=val_step,len+=(pixels_for_symbol+vert_step_in_pixels))
    {
	sprintf(s,"%.10lf",value);
	Sensor::GetRealNumEnd(s,s_end);
	cairo_move_to(img,offset+max_num_length-static_cast<int>(s_end-s)*(pixels_for_symbol-4),len);
	cairo_show_text(img,s);
    }
    
    free(s);
    
    /* рисование самих графиков */
    const double dat_scale=static_cast<double>(6*pixels_for_symbol+8+hor_step_in_pixels)/per_step;
    val_step=static_cast<double>(height-offset-offset-9)/(max_val-min_val);
    if (verbose) putc('\n',stdout);
    if (mean_period==0xffffffff)
    {
	//srand(0);
	for (it1=sens_list.begin(),it1_end=sens_list.end(); it1!=it1_end; ++it1)
	{
	    const Sensor &ss=sensors[it1->first];
	    for (sens_num=0u; sens_num!=it1->second.size(); ++sens_num)
	    {
		const Date &d=it1->second[sens_num];
		if (verbose)
		{
		    unsigned int _1,_2,_3;
		    days_to_date(d.days,_1,_2,_3);
		    printf("Drawing graph for sensor #%u [%u-%u%u-%u%u ",it1->first,_1,_2/10u,_2%10u,_3/10u,_3%10u);
		    seconds_to_time(d.seconds,_1,_2,_3);
		    printf("%u%u:%u%u:%u%u]... ",_1/10u,_1%10u,_2/10u,_2%10u,_3/10u,_3%10u);
		}
		cairo_set_source_rgba(img,(rand()%256u)/255.0,(rand()%256u)/255.0,(rand()%256u)/255.0,1.0);
		time=d.seconds+period;
		seconds=time/86400u;
		time-=(seconds*86400u);
		ss.Draw(img,d.days,d.seconds,d.days+seconds,time,offset+max_num_length+3*pixels_for_symbol+4+5,
			dat_scale,max_val,offset+5,val_step,height-offset);
		cairo_stroke(img);
		if (verbose) printf("done.\n");
	    }
	}
    }
    else
    {
	value=200.0/255.0;
	cairo_set_source_rgba(img,value,value,value,1.0);
	for (it1=sens_list.begin(),it1_end=sens_list.end(); it1!=it1_end; ++it1)
	{
	    const Sensor &ss=sensors[it1->first];
	    for (sens_num=0u; sens_num!=it1->second.size(); ++sens_num)
	    {
		const Date &d=it1->second[sens_num];
		time=d.seconds+period;
		seconds=time/86400u;
		time-=(seconds*86400u);
		ss.DrawAvg1(img,d.days,d.seconds,d.days+seconds,time,mean_period,
			    offset+max_num_length+3*pixels_for_symbol+4+5,
			    dat_scale,max_val,offset+5,val_step,height-offset-3);
	    }
	}
	for (it1=sens_list.begin(),it1_end=sens_list.end(); it1!=it1_end; ++it1)
	{
	    const Sensor &ss=sensors[it1->first];
	    for (sens_num=0u; sens_num!=it1->second.size(); ++sens_num)
	    {
		const Date &d=it1->second[sens_num];
		if (verbose)
		{
		    unsigned int _1,_2,_3;
		    days_to_date(d.days,_1,_2,_3);
		    printf("Drawing avg-graph for sensor #%u [%u-%u%u-%u%u ",it1->first,_1,_2/10u,_2%10u,_3/10u,_3%10u);
		    seconds_to_time(d.seconds,_1,_2,_3);
		    printf("%u%u:%u%u:%u%u]... ",_1/10u,_1%10u,_2/10u,_2%10u,_3/10u,_3%10u);
		}
		cairo_set_source_rgba(img,(rand()%256u)/255.0,(rand()%256u)/255.0,(rand()%256u)/255.0,1.0);
		time=d.seconds+period;
		seconds=time/86400u;
		time-=(seconds*86400u);
		ss.DrawAvg2(img,d.days,d.seconds,d.days+seconds,time,mean_period,
			    offset+max_num_length+3*pixels_for_symbol+4+5,
			    dat_scale,max_val,offset+5,val_step,height-offset);
		cairo_stroke(img);
		if (verbose) printf("done.\n");
	    }
	}
    }
    
    cairo_surface_flush(img_surface);
    if (png_fmt) cairo_surface_write_to_png(img_surface,outp_file);
    
    cairo_destroy(img);
    cairo_surface_destroy(img_surface);
    
    if (verbose) putc('\n',stdout);
    
    free(outp_file);
    
    return 0;
}

// перевод даты в формате гггг-мм-дд в дни;
// единственное требование: 1<='months'<=12
inline unsigned int date_to_days (const unsigned int years, const unsigned int months, const unsigned int days) {
    unsigned int ans=days,i;
    
    if (years>1u)
    {
	i=years-1u;
	ans+=365u*i+i/4u-i/100u+i/400u;
    }
    if (months==2u) ans+=31u;
    else
    {
	if (months!=1u)
	{
	    ans+=(((years % 400u)==0u) || (((years & 0x3)==0u) && ((years % 100u)!=0u)))? 60u : 59u;
	    for (i=3u; i!=months; ++i)
		ans+=((i<8u) ^ ((i & 0x1)==0u))? 31u : 30u;
	}
    }
    return ans;
}

// перевод даты в днях в формат гггг-мм-дд
inline void days_to_date (const unsigned int days, unsigned int &year, unsigned int &month, unsigned int &day) {
    unsigned int cur=days,add;
    
    for (year=1u; cur>366u; ++year)
	cur-=(((year % 400u)==0u) || (((year & 0x3)==0u) && ((year % 100u)!=0u)))? 366u : 365u;
    add=(((year % 400u)==0u) || (((year & 0x3)==0u) && ((year % 100u)!=0u)))? 1u : 0u;
    if (cur==366u)
    {
	if (add==1u)
	{
	    month=12u;
	    day=31u;
	}
	else
	{
	    ++year;
	    month=1u;
	    day=1u;
	}
	return;
    }
    if (cur>181u+add)
    {
	if (cur>273u+add)
	{
	    if (cur>334u+add)
	    {
		month=12u;
		day=cur-(334u+add);
	    }
	    else
		if (cur>304u+add)
		{
		    month=11u;
		    day=cur-(304u+add);
		}
		else
		{
		    month=10u;
		    day=cur-(273u+add);
		}
	}
	else
	{
	    if (cur>243u+add)
	    {
		month=9u;
		day=cur-(243u+add);
	    }
	    else
		if (cur>212u+add)
		{
		    month=8u;
		    day=cur-(212u+add);
		}
		else
		{
		    month=7u;
		    day=cur-(181u+add);
		}
	}
    }
    else
    {
	if (cur>90u+add)
	{
	    if (cur>151u+add)
	    {
		month=6u;
		day=cur-(151u+add);
	    }
	    else
		if (cur>120u+add)
		{
		    month=5u;
		    day=cur-(120u+add);
		}
		else
		{
		    month=4u;
		    day=cur-(90u+add);
		}
	}
	else
	{
	    if (cur>59u+add)
	    {
		month=3u;
		day=cur-(59u+add);
	    }
	    else
		if (cur>31u)
		{
		    month=2u;
		    day=cur-31u;
		}
		else
		{
		    month=1u;
		    day=cur;
		}
	}
    }
}

// перевод времени в формате чч:мм:сс в секунды
inline unsigned int time_to_seconds (const unsigned int hours, const unsigned int minutes, const unsigned int seconds) {
    return ((hours*60u+minutes)*60u+seconds);
}

// перевод времени в секундах в формат чч:мм:сс
inline void seconds_to_time (const unsigned int seconds, unsigned int &hour, unsigned int &minute, unsigned int &second) {
    const unsigned mins=seconds/60u;
    hour=mins/60u;
    minute=mins-hour*60u;
    second=seconds-mins*60u;
}

// получение временного интервала из командной строки
unsigned int get_time_intl_in_seconds (const char *s) {
    unsigned int hours=0u,minutes=0u,seconds=0u;
        
    /* проверяем всю строку и заодно считаем количество знаков '-' */
    if (strlen(s)>32lu) return 0xffffffff; // явно будут переполнения
    for (const char *cur=s; *cur!='\0'; ++cur)
    {
	if (*cur=='-')
	{
	    ++seconds;
	    continue;
	}
	if ((*cur<'0') || (*cur>'9')) return 0xffffffff; // нецифра
    }
    if (seconds>2u) return 0xffffffff; // слишком много '-'

    /* парсим */
    char c[35];
        
    switch (sscanf(s,"%u-%u-%u",&hours,&minutes,&seconds))
    {
      case 1:
	  sprintf(c,"%u",hours);
	  return (strcmp(s,c)? 0xffffffff : hours);
      case 2:
	  sprintf(c,"%u-%u",hours,minutes);
	  if (strcmp(s,c)) return 0xffffffff; // переполнение
	  seconds=hours*60u+minutes;
	  return (((seconds/60u==hours) && (seconds%60u==minutes))? seconds : 0xffffffff);
      case 3:
	  sprintf(c,"%u-%u-%u",hours,minutes,seconds);
	  if (strcmp(s,c)) return 0xffffffff; // переполнение
	  {const unsigned int tmp=(hours*60u+minutes)*60u+seconds;
	  return (((tmp/3600u==hours) && ((tmp/60u)%60u==minutes) && (tmp%60u==seconds))? tmp : 0xffffffff);}
      default: return 0xffffffff; // ошибка
    }
}

// построение списка запросов на графики датчиков
void build_sens_list_query (const char* &s, std::map<unsigned int,std::vector<Date> > &sens_list) {
    unsigned int sens_num;
    unsigned int y,m,d,h,mi,se;
    
    for ( ; (*s!='\0') && ((*s<'0') || (*s>'9')); ++s) ;
    if (*s=='\0') return; // ошибка

    // ID датчика
    sens_num=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(sens_num*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка

    for (++s; (*s!='\0') && ((*s<'0') || (*s>'9')); ++s) ;
    if (*s=='\0') return; // ошибка
    y=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(y*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка
    ++s;
    m=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(m*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка
    ++s;
    d=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(d*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка
    ++s;
    h=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(h*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка
    ++s;
    mi=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(mi*=10u)+=(*s-'0');
    if (*s=='\0') return; // ошибка
    ++s;
    se=*s-'0';
    for (++s; (*s>='0') && (*s<='9'); ++s)
    	(se*=10u)+=(*s-'0');

    if ((m==0u) || (m>12u)) return; // ошибка
    
    Date dat;
    dat.days=date_to_days(y,m,d);
    dat.seconds=time_to_seconds(h,mi,se);
    sens_list[sens_num].push_back(dat);
}

// считывание строки из текствого файла ('\n' затирается);
// последний вызов должен быть с NULL в качестве входного аргумента!
char* getline (FILE *f) {
    static const size_t read=20u;
    static size_t mem_len=read;
    static char *s=static_cast<char*>(malloc(mem_len*sizeof(char)));
    size_t len;
    char *cur=s;
    
    if (f==NULL)
    {
	free(s);
	return NULL;
    }
    *s='\0';
    while (fgets(cur,read,f)!=NULL)
    {
	for ( ; *cur!='\0'; ++cur)
	{
	    if (*cur=='\n')
	    {
		*cur='\0';
		return ((*s=='\0')? NULL : s);
	    }
	}
	len=static_cast<size_t>(cur-s);
	if (len+read>=mem_len)
	{
	    mem_len<<=1u;
	    s=static_cast<char*>(realloc(s,mem_len*sizeof(char)));
	    if (s==NULL)
	    {
		printf("\nThere is not enough memory. Can't proceed\n\n");
		return NULL;
	    }
	    cur=s+len;
	}
    }
    return ((*s=='\0')? NULL : s);
}

// разбор одной строки лог-файла
bool parse_log_line (const char *s, unsigned int &days, unsigned int &seconds, 
		      const std::map<unsigned int,std::vector<Date> > &sens_list, 
		      unsigned int *sens_num, bool &undefined, double *value) {
    static const char *str_sensor="sensor(";
    static const int str_sensor_len=7;
    const char *cur1,*cur2;
    unsigned int u,m,l,add_d;
    
    // {year,month,day}
    cur1=strchr(s,'{');
    if (cur1==NULL) return false;
    ++cur1;
    cur2=strchr(cur1,',');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&u)<1) return false;
    cur1=cur2+1;
    cur2=strchr(cur1,',');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&m)<1) return false;
    if ((m==0u) || (m>12u)) return false;
    cur1=cur2+1;
    cur2=strchr(cur1,'}');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&l)<1) return false;
    if (l==0u) return false;
    if (m==2u)
	add_d=(((u % 400u)==0u) || (((u & 0x3)==0u) && ((u % 100u)!=0u)))? 1u : 0u;
    else
	add_d=((m<8u) ^ ((m & 0x1)==0u))? 3u : 2u;
    if (l>28u+add_d) return false;
    days=date_to_days(u,m,l);
    
    // {hours,minutes,seconds}
    cur1=strchr(cur2+1,'{');
    if (cur1==NULL) return false;
    ++cur1;
    cur2=strchr(cur1,',');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&u)<1) return false;
    if (u>23u) return false;
    cur1=cur2+1;
    cur2=strchr(cur1,',');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&m)<1) return false;
    if (m>59u) return false;
    cur1=cur2+1;
    cur2=strchr(cur1,'}');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",&l)<1) return false;
    if (l>59u) return false;
    seconds=time_to_seconds(u,m,l);
    
    // sensor(N) = XXX
    cur1=strstr(cur2+1,str_sensor);
    if (cur1==NULL) return false;
    cur1+=str_sensor_len;
    cur2=strchr(cur1,')');
    if (cur2==NULL) return false;
    if (sscanf(cur1,"%u",sens_num)<1) return false;
    if (sens_list.find(*sens_num)==sens_list.end()) return false;
    cur1=strchr(cur2+1,'=');
    if (cur1==NULL) return false;
    ++cur1;
    for ( ; ; )
    {
	if (*cur1=='\0') return false;
	if (!((*cur1==' ') || (*cur1=='\t') || (*cur1=='\f') || (*cur1=='\v') || (*cur1=='\r')))
	    break;
	++cur1;
    }
    undefined=((*cur1=='u') && !strcmp(cur1+1,"ndefined"));
    if (!undefined && (sscanf(cur1,"%lf",value)<1)) return false;
        
    return true;
}
